package com.sky.aspect;


import com.alibaba.fastjson.JSON;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MemberSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面类,实现公共字段自动填充的逻辑
 */
@Component
@Slf4j
@Aspect
public class AutoFillAspect {


    //切入点    com.sky.mapper.*.*(..)在这个包下的所有字段和所有方法

    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill )")
    public void autoFillPointCut() {
    }


    /**
     * 前置通知,在通知中进行公共字段的赋值
     *
     * @param joinPoint 拦截到的方法
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段的填充");
        //获取数据库操作类型
        /*
        * 什么是签名
        *方法签名（Method Signature）是方法的标识特征，包含了以下信息：
        *public String doSomething(String name, int age) throws Exception
         // ↑访问修饰符 ↑返回类型 ↑方法名 ↑参数列表     ↑异常声明
        *
        * */


        /*
        * 为什么不直接调用joinPoint获取getMethod
        *joinPoint 确实包含了连接点的所有信息，但它提供的是一个通用的接口：
        *
        * public interface JoinPoint {
    String toString();         // 连接点所在位置的相关信息
    String toShortString();    // 连接点所在位置的简短相关信息
    String toLongString();     // 连接点所在位置的完整相关信息
    Object getThis();          // 获取AOP代理对象
    Object getTarget();        // 获取目标对象
    Object[] getArgs();        // 获取参数列表
    Signature getSignature();  // 获取连接点的签名
    SourceLocation getSourceLocation(); // 获取连接点方法所在类文件中的位置
    String getKind();          // 获取连接点类型
    StaticPart getStaticPart(); // 获取连接点静态部分
}
        * */


        /*
         为什么要把signature转成MethodSignature 因为signature只是一个接口
         Signature 继承关系
Signature (接口)
├── MethodSignature     // 用于方法的签名
├── ConstructorSignature // 用于构造器的签名
└── FieldSignature      // 用于字段的签名
        * */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
        OperationType value = autoFill.value();//获取数据库的操作类型


        //获取到拦截器拦截方法的参数 - 实体对象
       Object[] args = joinPoint.getArgs();
            if (args==null||args.length==0){
                return;
            }
         Object entity =args[0];
       //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射赋值
        if (value==OperationType.INSERT){
            //为四个对象赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象属性赋值--因为不确定是那一类的实体  如若不利用反射的话需要利用if语句来判断十分的繁琐
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if (value==OperationType.UPDATE){
            //为2个公共字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        }

}
